import os
import sys
import re
import docx2txt
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from tkinter.scrolledtext import ScrolledText
import threading
import time
import json
from docx import Document

class ProductTextChunker:
    """产品文档分块器，用于处理产品说明、手册等文档的分块"""
    
    def __init__(self, master):
        """初始化分块器UI界面"""
        self.master = master
        self.master.title("产品文档分块工具")
        self.master.geometry("800x600")
        self.master.resizable(True, True)
        
        # 设置主窗口样式
        self.style = ttk.Style()
        self.style.configure('TButton', font=('微软雅黑', 10))
        self.style.configure('TLabel', font=('微软雅黑', 10))
        
        # 创建主框架
        self.main_frame = ttk.Frame(self.master, padding="10")
        self.main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 创建顶部操作区域
        self.top_frame = ttk.Frame(self.main_frame)
        self.top_frame.pack(fill=tk.X, padx=5, pady=5)
        
        # 文件选择框架
        self.file_frame = ttk.LabelFrame(self.top_frame, text="文件选择", padding="5")
        self.file_frame.pack(fill=tk.X, padx=5, pady=5)
        
        # 文件路径输入框
        self.path_var = tk.StringVar()
        self.path_entry = ttk.Entry(self.file_frame, textvariable=self.path_var, width=50)
        self.path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5, pady=5)
        
        # 选择文件按钮
        self.select_file_btn = ttk.Button(self.file_frame, text="选择文件", command=self.select_file)
        self.select_file_btn.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 批量选择文件按钮
        self.select_multiple_files_btn = ttk.Button(self.file_frame, text="批量选择", command=self.select_multiple_files)
        self.select_multiple_files_btn.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 选择文件夹按钮
        self.select_folder_btn = ttk.Button(self.file_frame, text="选择文件夹", command=self.select_folder)
        self.select_folder_btn.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 输出目录框架
        self.output_frame = ttk.LabelFrame(self.top_frame, text="输出目录", padding="5")
        self.output_frame.pack(fill=tk.X, padx=5, pady=5)
        
        # 输出目录输入框
        self.output_var = tk.StringVar()
        self.output_entry = ttk.Entry(self.output_frame, textvariable=self.output_var, width=50)
        self.output_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5, pady=5)
        
        # 选择输出目录按钮
        self.select_output_btn = ttk.Button(self.output_frame, text="选择输出目录", command=self.select_output_dir)
        self.select_output_btn.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 创建分块参数框架
        self.param_frame = ttk.LabelFrame(self.top_frame, text="分块参数", padding="5")
        self.param_frame.pack(fill=tk.X, padx=5, pady=5)
        
        # 分块模式
        ttk.Label(self.param_frame, text="分块模式:").pack(side=tk.LEFT, padx=5, pady=5)
        self.chunk_mode_var = tk.StringVar(value="按分隔符")
        self.chunk_mode_combo = ttk.Combobox(self.param_frame, textvariable=self.chunk_mode_var, width=15)
        self.chunk_mode_combo['values'] = ('按分隔符', '智能分块')
        self.chunk_mode_combo.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 分隔符设置
        ttk.Label(self.param_frame, text="分隔符:").pack(side=tk.LEFT, padx=5, pady=5)
        self.separator_var = tk.StringVar(value="#")
        self.separator_entry = ttk.Entry(self.param_frame, textvariable=self.separator_var, width=5)
        self.separator_entry.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 空行数量设置
        ttk.Label(self.param_frame, text="空行数量:").pack(side=tk.LEFT, padx=5, pady=5)
        self.blank_line_count_var = tk.IntVar(value=2)
        self.blank_line_count_spinbox = ttk.Spinbox(self.param_frame, from_=1, to=5, textvariable=self.blank_line_count_var, width=5)
        self.blank_line_count_spinbox.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 最小内容长度（决定是否拆分为独立块）
        ttk.Label(self.param_frame, text="最小内容长度:").pack(side=tk.LEFT, padx=5, pady=5)
        self.min_content_length_var = tk.IntVar(value=100)
        self.min_content_length_spinbox = ttk.Spinbox(self.param_frame, from_=50, to=500, increment=50, textvariable=self.min_content_length_var, width=5)
        self.min_content_length_spinbox.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 超时设置（秒）
        ttk.Label(self.param_frame, text="超时(秒):").pack(side=tk.LEFT, padx=5, pady=5)
        self.timeout_var = tk.IntVar(value=60)
        self.timeout_spinbox = ttk.Spinbox(self.param_frame, from_=10, to=300, increment=10, textvariable=self.timeout_var, width=5)
        self.timeout_spinbox.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 输出文件格式
        ttk.Label(self.param_frame, text="输出格式:").pack(side=tk.LEFT, padx=5, pady=5)
        self.output_format_var = tk.StringVar(value="txt")
        self.output_format_combo = ttk.Combobox(self.param_frame, textvariable=self.output_format_var, width=8)
        self.output_format_combo['values'] = ('txt', 'json', 'md', 'docx')
        self.output_format_combo.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 按钮框架
        self.button_frame = ttk.Frame(self.top_frame)
        self.button_frame.pack(fill=tk.X, padx=5, pady=5)
        
        # 开始分块按钮
        self.start_btn = ttk.Button(self.button_frame, text="开始分块", command=self.start_chunking)
        self.start_btn.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 停止按钮
        self.stop_btn = ttk.Button(self.button_frame, text="停止处理", command=self.stop_chunking, state="disabled")
        self.stop_btn.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 清除日志按钮
        self.clear_log_btn = ttk.Button(self.button_frame, text="清除日志", command=self.clear_log)
        self.clear_log_btn.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 状态框架
        self.status_frame = ttk.LabelFrame(self.main_frame, text="处理进度", padding="5")
        self.status_frame.pack(fill=tk.X, padx=5, pady=5)
        
        # 进度框架
        self.progress_frame = ttk.Frame(self.status_frame)
        self.progress_frame.pack(fill=tk.X, padx=5, pady=5)
        
        # 进度条 - 增加高度和改进样式
        self.style.configure("Chunking.Horizontal.TProgressbar", thickness=20, background='#4CAF50')
        self.progress = ttk.Progressbar(self.progress_frame, orient="horizontal", 
                                       length=100, mode="determinate", 
                                       style="Chunking.Horizontal.TProgressbar")
        self.progress.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5, pady=5)
        
        # 百分比标签
        self.percent_var = tk.StringVar(value="0%")
        self.percent_label = ttk.Label(self.progress_frame, textvariable=self.percent_var, width=8)
        self.percent_label.pack(side=tk.LEFT, padx=5, pady=5)
        
        # 文件计数标签框架
        self.count_frame = ttk.Frame(self.status_frame)
        self.count_frame.pack(fill=tk.X, padx=5, pady=2)
        
        # 当前文件计数
        self.file_count_var = tk.StringVar(value="0/0 个文件")
        self.file_count_label = ttk.Label(self.count_frame, textvariable=self.file_count_var)
        self.file_count_label.pack(side=tk.LEFT, padx=5, pady=2)
        
        # 处理块计数
        self.chunk_count_var = tk.StringVar(value="总计 0 个块")
        self.chunk_count_label = ttk.Label(self.count_frame, textvariable=self.chunk_count_var)
        self.chunk_count_label.pack(side=tk.RIGHT, padx=5, pady=2)
        
        # 状态标签
        self.status_var = tk.StringVar(value="就绪")
        self.status_label = ttk.Label(self.status_frame, textvariable=self.status_var)
        self.status_label.pack(fill=tk.X, padx=5, pady=2)
        
        # 日志框架
        self.log_frame = ttk.LabelFrame(self.main_frame, text="处理日志", padding="5")
        self.log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 日志文本框
        self.log_text = ScrolledText(self.log_frame, wrap=tk.WORD)
        self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 文件列表
        self.file_list = []
        
        # 处理线程
        self.processing_thread = None
        
        # 初始化输出目录
        default_output_dir = os.path.join(os.getcwd(), "processed")
        self.output_var.set(default_output_dir)
        
    def select_file(self):
        """选择单个文件"""
        file_path = filedialog.askopenfilename(
            filetypes=[("文档文件", "*.docx *.doc *.txt *.rtf"), ("Word文档", "*.docx *.doc"), 
                      ("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if file_path:
            self.path_var.set(file_path)
            self.file_list = [file_path]
            self.log(f"已选择文件: {file_path}")
    
    def select_multiple_files(self):
        """批量选择多个文件"""
        file_paths = filedialog.askopenfilenames(
            filetypes=[("文档文件", "*.docx *.doc *.txt *.rtf"), ("Word文档", "*.docx *.doc"), 
                      ("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if file_paths:
            # 将元组转换为列表
            self.file_list = list(file_paths)
            # 在输入框中显示文件数量信息
            self.path_var.set(f"已选择 {len(self.file_list)} 个文件")
            self.log(f"已批量选择 {len(self.file_list)} 个文件")
            # 在日志中列出所有选择的文件
            for idx, file_path in enumerate(self.file_list):
                self.log(f"  {idx+1}. {os.path.basename(file_path)}")
    
    def select_folder(self):
        """选择文件夹"""
        folder_path = filedialog.askdirectory()
        if folder_path:
            self.path_var.set(folder_path)
            self.file_list = []
            # 获取文件夹中所有的docx文件
            for root, _, files in os.walk(folder_path):
                for file in files:
                    if file.endswith((".docx", ".doc", ".txt")):
                        self.file_list.append(os.path.join(root, file))
            
            self.log(f"已选择文件夹: {folder_path}")
            self.log(f"找到 {len(self.file_list)} 个文档文件")
    
    def select_output_dir(self):
        """选择输出目录"""
        output_dir = filedialog.askdirectory()
        if output_dir:
            self.output_var.set(output_dir)
            self.log(f"输出目录: {output_dir}")
    
    def log(self, message):
        """添加日志信息"""
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.see(tk.END)
    
    def clear_log(self):
        """清除日志"""
        self.log_text.delete(1.0, tk.END)
    
    def update_status(self, message, progress_value=None):
        """更新状态信息和进度条"""
        self.status_var.set(message)
        if progress_value is not None:
            self.progress["value"] = progress_value
            # 计算并更新百分比
            if self.progress["maximum"] > 0:
                percent = int((progress_value / self.progress["maximum"]) * 100)
                self.percent_var.set(f"{percent}%")
            else:
                self.percent_var.set("0%")
        self.master.update_idletasks()
    
    def update_file_count(self, current, total):
        """更新文件计数"""
        self.file_count_var.set(f"{current}/{total} 个文件")
        self.master.update_idletasks()
    
    def update_chunk_count(self, count):
        """更新块计数"""
        self.chunk_count_var.set(f"总计 {count} 个块")
        self.master.update_idletasks()
    
    def get_heading_level(self, line):
        """
        获取标题的级别
        返回: (级别, 匹配标题)
        级别: 1表示一级标题, 2表示二级标题, 3表示三级标题, 0表示非标题行
        """
        # 所有需要作为分块点的标记
        # 中文数字加顿号（如 一、二、三、等）
        if re.match(r'^[一二三四五六七八九十]+、', line):
            self.log(f"识别到中文数字标题: {line}")
            return (1, line)
            
        # 阿拉伯数字加顿号（如 1、2、3、等）
        elif re.match(r'^\d+、', line):
            self.log(f"识别到数字顿号标题: {line}")
            return (1, line)
            
        # 第X章 或 章节标题
        elif re.match(r'^第[一二三四五六七八九十]+章', line) or re.match(r'^第\d+章', line):
            self.log(f"识别到章节标题: {line}")
            return (1, line)
            
        # 纯数字序号（如 1 2 3）
        elif re.match(r'^\d+\s+[^\d\.\(\)]', line):
            self.log(f"识别到纯数字序号标题: {line}")
            return (1, line)
            
        # 第一条、第二条...
        elif re.match(r'第[一二三四五六七八九十]+条', line) or re.match(r'第\d+条', line):
            self.log(f"识别到条款标题: {line}")
            return (1, line)
            
        # 数字加英文句号（如 1. 2. 3. 等，强化此模式，修改为更宽松的匹配方式）
        elif re.match(r'^\s*\d+\.\s*', line):
            self.log(f"识别到数字加英文句号标题: {line}")
            return (1, line)
            
        # 带小数点的数字加顿号（如 1.2、2.3、等）
        elif re.match(r'^\d+\.\d+、', line):
            self.log(f"识别到小数点数字加顿号标题: {line}")
            return (1, line)
            
        # 带小数点的数字（如 1.1、2.3）- 修改为更宽松的匹配
        elif re.match(r'^\s*\d+\.\d+', line):
            self.log(f"识别到小数点数字标题: {line}")
            return (1, line)
            
        # 中文括号数字（如 （一）、（二）等）
        elif re.match(r'（[一二三四五六七八九十]+）', line):
            self.log(f"识别到中文括号标题: {line}")
            return (1, line)
            
        # 英文括号 + 中文数字（如 (一)、(二) 等）
        elif re.match(r'\([一二三四五六七八九十]+\)', line):
            self.log(f"识别到英文括号中文数字标题: {line}")
            return (1, line)
            
        # 带英文括号的数字（如 (1)、(2) 等）
        elif re.match(r'^\(\d+\)', line):
            self.log(f"识别到英文括号数字标题: {line}")
            return (1, line)
            
        # 带全角括号的数字（如 （1）、（2） 等）
        elif re.match(r'^（\d+）', line):
            self.log(f"识别到全角括号数字标题: {line}")
            return (1, line)
            
        # 带两级小数点的数字（如 1.2.3）- 强化此模式
        elif re.match(r'^\s*\d+\.\d+\.\d+', line):
            self.log(f"识别到多级小数点标题: {line}")
            return (1, line)
            
        # 带两级小数点结尾的数字（如 1.2.）- 强化此模式
        elif re.match(r'^\s*\d+\.\d+\.', line):
            self.log(f"识别到多级小数点结尾标题: {line}")
            return (1, line)
            
        # 字母加英文句号（如 a. b. c. ... 或 A. B. C. ...）- 强化此模式
        elif re.match(r'^\s*[a-zA-Z]\.\s*', line):
            self.log(f"识别到字母加英文句号标题: {line}")
            return (1, line)
            
        # 括号字母（如 (a) (b) (c) ... 或 (A) (B) (C) ...）
        elif re.match(r'^\([a-zA-Z]\)', line):
            self.log(f"识别到括号字母标题: {line}")
            return (1, line)
            
        # 带圈数字（如 ① ② ③ ...）
        elif re.search(r'^[①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳]', line):
            self.log(f"识别到带圈数字标题: {line}")
            return (1, line)
            
        # 单个数字开头的行（如 2 开头，但不能是时间或数量等）
        elif re.match(r'^\s*\d+(?!\d|:|年|月|日|时|分|秒|元|￥|\$|%)', line) and len(line.strip()) > 2:
            self.log(f"识别到单个数字开头标题: {line}")
            return (1, line)
            
        # 其他情况
        else:
            return (0, None)
    
    def _fallback_chunk_by_major_titles(self, lines):
        """备用分块方法：简单地按照标题分块，包括数字标题和括号标题"""
        self.log("使用备用分块方法 - 识别所有级别的标题作为分块点")
        self.log("此方法将识别中文数字、阿拉伯数字、小数点数字和各种括号标题")
        
        chunks = []
        current_chunk = []
        
        # 第一行通常是文档标题，单独成块
        if lines and len(lines) > 0:
            chunks.append(lines[0])
            start_idx = 1
        else:
            start_idx = 0
        
        # 记录标题识别统计
        title_types = {
            "中文数字标题": 0,
            "数字顿号标题": 0,
            "章节标题": 0,
            "纯数字序号标题": 0,
            "条款标题": 0,
            "数字加英文句号标题": 0,
            "小数点数字标题": 0,
            "括号标题": 0,
            "多级小数点标题": 0,
            "字母标题": 0,
            "带圈数字标题": 0,
            "单个数字开头标题": 0
        }
        
        # 按所有类型的标题分块
        for i in range(start_idx, len(lines)):
            line = lines[i]
            level, _ = self.get_heading_level(line)
            
            # 任何级别的标题都开始新块
            if level > 0:
                # 分析并记录标题类型
                if re.match(r'^[一二三四五六七八九十]+、', line):
                    title_types["中文数字标题"] += 1
                elif re.match(r'^\d+、', line):
                    title_types["数字顿号标题"] += 1
                elif re.match(r'^第[一二三四五六七八九十]+章', line) or re.match(r'^第\d+章', line):
                    title_types["章节标题"] += 1
                elif re.match(r'^\d+\s+[^\d\.\(\)]', line):
                    title_types["纯数字序号标题"] += 1
                elif re.match(r'第[一二三四五六七八九十]+条', line) or re.match(r'第\d+条', line):
                    title_types["条款标题"] += 1
                elif re.match(r'^\s*\d+\.\s*', line):
                    title_types["数字加英文句号标题"] += 1
                elif re.match(r'^\s*\d+\.\d+', line):
                    title_types["小数点数字标题"] += 1
                elif re.match(r'^\(', line) or re.match(r'^（', line):
                    title_types["括号标题"] += 1
                elif re.match(r'^\s*\d+\.\d+\.\d+', line) or re.match(r'^\s*\d+\.\d+\.', line):
                    title_types["多级小数点标题"] += 1
                elif re.match(r'^\s*[a-zA-Z]', line):
                    title_types["字母标题"] += 1
                elif re.search(r'^[①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳]', line):
                    title_types["带圈数字标题"] += 1
                elif re.match(r'^\s*\d+(?!\d|:|年|月|日|时|分|秒|元|￥|\$|%)', line):
                    title_types["单个数字开头标题"] += 1
                
                self.log(f"备用方法中识别到标题: {line}")
                if current_chunk:
                    chunks.append('\n'.join(current_chunk))
                current_chunk = [line]
            else:
                current_chunk.append(line)
        
        # 添加最后一个块
        if current_chunk:
            chunks.append('\n'.join(current_chunk))
        
        # 输出标题统计信息
        self.log("备用方法标题识别统计:")
        for title_type, count in title_types.items():
            if count > 0:
                self.log(f"  - {title_type}: {count} 个")
        
        self.log(f"备用方法分块完成，共生成{len(chunks)}个块")
        return chunks
    
    def smart_chunk_text(self, text):
        """
        智能分块产品文档
        考虑标题级别关系和内容长度
        """
        # 添加超时保护
        start_time = time.time()
        max_processing_time = self.timeout_var.get()  # 最大处理时间，单位秒
        
        lines = [line.strip() for line in text.split('\n') if line.strip()]
        
        # 获取最小内容长度阈值
        min_content_length = self.min_content_length_var.get()
        
        # 初始化结果变量
        chunks = []
        current_chunk = []
        current_level = 0
        
        # 记录处理进度
        self.log(f"开始智能分块，共{len(lines)}行文本")
        self.log("所有识别到的标记都将作为分块点，包括：")
        self.log("  - 中文数字标题（如 一、二、三、等）")
        self.log("  - 阿拉伯数字标题（如 1. 2. 3. 等，无论是否有空格）")
        self.log("  - 小数点数字标题（如 1.1 1.2 等）")
        self.log("  - 括号标题（如 (1) (2) (a) (b) 等）")
        self.log("  - 多级小数点标题（如 1.1.1 1.1.2 等）")
        self.log("  - 带圈数字标题（如 ① ② ③ 等）")
        
        # 检查是否是产品文档 - 产品文档一般不是通知类文件，直接使用标准处理
        is_product_doc = False
        for i in range(min(3, len(lines))):
            if any(keyword in lines[i] for keyword in ['产品', '说明书', '手册', '指南', '用户', '使用']):
                is_product_doc = True
                self.log("检测到产品文档，使用标准分块逻辑")
                break
        
        # 如果检测到是通知类文件，并且不是产品文档，则使用简化分块逻辑
        if len(lines) > 0 and ('通知' in lines[0] or '〔' in lines[0] or '号' in lines[0]) and not is_product_doc:
            self.log("检测到通知类文档，使用简化分块逻辑")
            
            # 处理通知类文档的特殊逻辑
            # 1. 第一行通常是标题，作为第一块
            if lines[0]:
                chunks.append(lines[0])
                
            # 2. 文号单独成块
            if len(lines) > 1 and ('〔' in lines[1] or '号' in lines[1]):
                chunks.append(lines[1])
                start_idx = 2
            else:
                start_idx = 1
                
            # 3. 处理一级标题 (一、二、...)和数字标题
            current_title_chunk = []
            for i in range(start_idx, len(lines)):
                line = lines[i]
                
                # 检查是否超时
                if time.time() - start_time > max_processing_time:
                    self.log("分块处理时间过长，采用简化处理")
                    # 简单按一级标题分块
                    return self._fallback_chunk_by_major_titles(lines)
                
                # 获取当前行的标题级别
                level, _ = self.get_heading_level(line)
                
                # 检查是否是任何级别的标题
                if level > 0:
                    # 保存之前的块
                    if current_title_chunk:
                        chunks.append('\n'.join(current_title_chunk))
                    current_title_chunk = [line]
                else:
                    current_title_chunk.append(line)
            
            # 添加最后一个标题块
            if current_title_chunk:
                chunks.append('\n'.join(current_title_chunk))
            
            self.log(f"简化分块完成，共生成{len(chunks)}个块")
            return chunks
        
        # 预处理：识别标题和特殊行
        doc_title_patterns = [
            r'^.*手册.*$',          # 产品手册标题
            r'^.*产品说明.*$',       # 产品说明标题
            r'^.*使用指南.*$',       # 使用指南标题
            r'^.*目录.*$',          # 目录标题
            r'^.*版本.*$',          # 版本信息
            r'^.*注意事项.*$',       # 注意事项
            r'^第\d+章\s+.*$',      # 第X章标题
            r'^第\d+节\s+.*$'       # 第X节标题
        ]
        
        # 目录模式
        toc_start_patterns = [
            r'^目\s*录$',
            r'^目\s*次$',
            r'^contents$',
            r'^index$'
        ]
        
        # 检查是否存在目录并提取
        in_toc = False
        toc_content = []
        
        # 第一遍：识别并提取目录
        j = 0
        while j < len(lines):
            # 检查是否超时
            if time.time() - start_time > max_processing_time:
                self.log("目录处理时间过长，跳过目录处理")
                break
                
            line = lines[j]
            
            # 检查是否是目录开始
            is_toc_start = False
            for pattern in toc_start_patterns:
                if re.match(pattern, line, re.IGNORECASE):
                    is_toc_start = True
                    break
            
            # 目录开始
            if is_toc_start and not in_toc:
                in_toc = True
                toc_content.append(line)
            
            # 已在目录中
            elif in_toc:
                # 检查是否到达目录结束（正文开始）
                if re.match(r'^第[一二三四五六七八九十0-9]+章', line) or re.match(r'^第[一二三四五六七八九十0-9]+节', line) or re.match(r'^\d+\s+', line):
                    in_toc = False
                    # 添加目录作为一个块
                    chunks.append('\n'.join(toc_content))
                    toc_content = []
                else:
                    toc_content.append(line)
            
            j += 1
        
        # 如果文档结束但目录还未关闭，添加目录
        if in_toc and toc_content:
            chunks.append('\n'.join(toc_content))
        
        # 第二遍：常规处理文档内容
        i = 0
        max_iterations = len(lines) * 2  # 设置最大迭代次数，防止死循环
        iteration_count = 0
        
        # 添加一个列表用于记录标题识别
        title_recognition_log = []
        
        while i < len(lines) and iteration_count < max_iterations:
            iteration_count += 1
            
            # 检查是否超时
            if time.time() - start_time > max_processing_time:
                self.log("分块处理时间过长，采用简化处理")
                # 如果已有一些结果，返回当前结果，否则使用备用方法
                if chunks:
                    return chunks
                else:
                    return self._fallback_chunk_by_major_titles(lines)
            
            line = lines[i]
            level, match = self.get_heading_level(line)
            
            # 记录标题识别日志
            if level > 0:
                title_recognition_log.append(f"行 {i+1}: '{line}' - 识别为分块点")
            
            # 检查是否是目录开始
            is_toc_start = False
            for pattern in toc_start_patterns:
                if re.match(pattern, line, re.IGNORECASE):
                    is_toc_start = True
                    break
            
            # 如果是目录部分，跳过（因为已在第一遍中处理）
            if is_toc_start:
                max_toc_skip = min(100, len(lines))
                toc_skip_count = 0
                
                while i < len(lines) and toc_skip_count < max_toc_skip:
                    next_line = lines[i]
                    if re.match(r'^第[一二三四五六七八九十0-9]+章', next_line) or re.match(r'^第[一二三四五六七八九十0-9]+节', next_line) or re.match(r'^\d+\s+', next_line):
                        break
                    i += 1
                    toc_skip_count += 1
                
                if toc_skip_count >= max_toc_skip:
                    self.log("目录跳过达到最大次数，可能存在无限循环")
                
                continue
            
            # 检查是否是产品说明标题或特殊行
            is_special_line = False
            for pattern in doc_title_patterns:
                if re.match(pattern, line):
                    is_special_line = True
                    break
            
            # 特殊行处理 - 单独成块
            if is_special_line:
                # 如果当前已有内容，则保存当前块
                if current_chunk:
                    chunks.append('\n'.join(current_chunk))
                    current_chunk = []
                # 特殊行单独成块
                chunks.append(line)
                i += 1
                continue
            
            # 标题处理 - 所有标题都作为分块点
            if level > 0:
                # 如果当前已有内容，则保存当前块
                if current_chunk:
                    chunks.append('\n'.join(current_chunk))
                    current_chunk = []
                
                current_level = level
                current_chunk.append(line)
                i += 1
            
            # 普通内容处理
            else:
                if not current_chunk:
                    # 如果当前块为空，开始新块
                    current_chunk = [line]
                else:
                    # 否则，添加到当前块
                    current_chunk.append(line)
                i += 1
        
        # 添加最后一个块
        if current_chunk:
            chunks.append('\n'.join(current_chunk))
        
        # 记录标题识别结果
        if title_recognition_log:
            self.log("标题识别日志:")
            for log_entry in title_recognition_log[:20]:  # 只记录前20个，避免日志过长
                self.log(log_entry)
            if len(title_recognition_log) > 20:
                self.log(f"还有 {len(title_recognition_log) - 20} 条标题识别记录未显示")
        
        # 处理小块内容合并（根据最小内容长度）
        if min_content_length > 0:
            merged_chunks = []
            temp_chunk = ""
            
            for chunk in chunks:
                if len(temp_chunk) == 0:
                    temp_chunk = chunk
                elif len(chunk) < min_content_length:
                    # 将小块合并到前一个块
                    temp_chunk += "\n" + chunk
                else:
                    # 保存当前临时块并开始新块
                    merged_chunks.append(temp_chunk)
                    temp_chunk = chunk
            
            # 添加最后一个临时块
            if temp_chunk:
                merged_chunks.append(temp_chunk)
            
            chunks = merged_chunks
        
        self.log(f"智能分块完成，共生成 {len(chunks)} 个块")
        return chunks
    
    def process_file(self, file_path, output_dir, chunk_mode, separator, blank_line_count):
        """处理单个文件，在分块位置添加空行"""
        try:
            # 创建输出目录
            if not os.path.exists(output_dir):
                os.makedirs(output_dir)
            
            # 获取文件名（不带扩展名）和扩展名
            file_name, file_ext = os.path.splitext(os.path.basename(file_path))
            file_ext = file_ext.lower()
            
            self.log(f"文件类型: {file_ext}")
            
            # 检查文件是否存在
            if not os.path.exists(file_path):
                raise FileNotFoundError(f"文件不存在: {file_path}")
            
            # 检查文件大小
            file_size = os.path.getsize(file_path) / 1024  # KB
            self.log(f"文件大小: {file_size:.2f} KB")
            
            if file_size == 0:
                raise ValueError("文件内容为空")
            
            # 提取文本 - 根据文件类型使用不同的方法
            text = ""
            extraction_success = False
            extraction_errors = []
            
            # 处理Word文档 (.docx, .doc)
            if file_ext in ['.docx', '.doc']:
                # 方法1: 使用docx2txt
                try:
                    self.log("尝试使用docx2txt提取文本...")
                    text = docx2txt.process(file_path)
                    if text.strip():
                        extraction_success = True
                        self.log("docx2txt提取成功")
                    else:
                        extraction_errors.append("docx2txt提取结果为空")
                except Exception as e:
                    extraction_errors.append(f"docx2txt提取失败: {str(e)}")
                
                # 方法2: 如果方法1失败，使用python-docx
                if not extraction_success:
                    try:
                        self.log("尝试使用python-docx提取文本...")
                        doc = Document(file_path)
                        paragraphs = [p.text for p in doc.paragraphs]
                        text = '\n'.join(paragraphs)
                        if text.strip():
                            extraction_success = True
                            self.log("python-docx提取成功")
                        else:
                            extraction_errors.append("python-docx提取结果为空")
                    except Exception as e:
                        extraction_errors.append(f"python-docx提取失败: {str(e)}")
            
            # 处理文本文件 (.txt, .rtf 或其他)
            else:
                try:
                    self.log("尝试直接读取文本文件...")
                    # 尝试不同的编码
                    encodings = ['utf-8', 'gbk', 'gb2312', 'latin1']
                    
                    for encoding in encodings:
                        try:
                            with open(file_path, 'r', encoding=encoding) as f:
                                text = f.read()
                            if text.strip():
                                extraction_success = True
                                self.log(f"以 {encoding} 编码成功读取文本文件")
                                break
                        except UnicodeDecodeError:
                            continue
                        except Exception as e:
                            extraction_errors.append(f"以 {encoding} 编码读取失败: {str(e)}")
                            break
                except Exception as e:
                    extraction_errors.append(f"读取文本文件失败: {str(e)}")
            
            # 如果常规方法都失败，尝试二进制方式读取
            if not extraction_success:
                try:
                    self.log("尝试以二进制方式读取文件...")
                    with open(file_path, 'rb') as f:
                        content = f.read()
                    
                    # 尝试不同的编码
                    for encoding in ['utf-8', 'gbk', 'gb2312', 'latin1']:
                        try:
                            text = content.decode(encoding)
                            if text.strip():
                                extraction_success = True
                                self.log(f"二进制读取成功，使用编码: {encoding}")
                                break
                        except UnicodeDecodeError:
                            continue
                except Exception as e:
                    extraction_errors.append(f"二进制读取失败: {str(e)}")
            
            # 检查是否成功提取文本
            if not extraction_success:
                error_msg = "无法提取文本内容: " + "; ".join(extraction_errors)
                self.log(error_msg)
                raise ValueError(error_msg)
            
            text_length = len(text)
            self.log(f"提取文本长度: {text_length} 字符")
            
            if text_length == 0:
                raise ValueError("提取的文本内容为空")
            
            # 替换可能干扰分块的控制字符
            text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', text)
            
            # 根据选择的模式进行分块
            self.log(f"使用 '{chunk_mode}' 模式进行分块...")
            if chunk_mode == '按分隔符':
                # 使用分隔符分块
                separator_count = text.count(separator)
                self.log(f"在文本中找到 {separator_count} 个分隔符 '{separator}'")
                
                if separator_count == 0:
                    self.log(f"警告: 在文本中未找到分隔符 '{separator}'，无法分块")
                    if text.strip():
                        # 如果至少有文本内容，就当作一个块处理
                        self.log("将整个文本作为一个块处理")
                        chunks = [text.strip()]
                    else:
                        raise ValueError(f"文本不包含分隔符 '{separator}' 且内容为空")
                else:
                    chunks = re.split(rf'{separator}', text)
                    # 移除空块
                    original_count = len(chunks)
                    chunks = [chunk.strip() for chunk in chunks if chunk.strip()]
                    if len(chunks) < original_count:
                        self.log(f"移除了 {original_count - len(chunks)} 个空块")
            else:  # 智能分块
                self.log("进行智能分块分析...")
                
                # 检查文本中是否存在标题结构
                title_patterns = [
                    r'^[一二三四五六七八九十]+、',   # 一级标题
                    r'第[一二三四五六七八九十]+条',  # 二级标题
                    r'（[一二三四五六七八九十]+）',  # 三级标题
                    r'^\d+\.',                    # 数字列表
                    r'^第[一二三四五六七八九十]+章'   # 章节标题
                ]
                
                pattern_matches = []
                chunks = []  # 初始化chunks变量，修复未定义错误
                
                for pattern in title_patterns:
                    matches = re.findall(pattern, text, re.MULTILINE)
                    if matches:
                        pattern_matches.append(f"{pattern}: {len(matches)} 匹配")
                
                if pattern_matches:
                    self.log(f"检测到标题结构: {', '.join(pattern_matches)}")
                else:
                    self.log("警告: 未检测到标准的标题结构，将使用段落分块")
                    # 使用空行作为分块依据（备用方法）
                    lines = text.split('\n')
                    chunks = []
                    current_chunk = []
                    
                    for line in lines:
                        if line.strip():
                            current_chunk.append(line)
                        elif current_chunk:  # 遇到空行且当前块不为空
                            chunks.append('\n'.join(current_chunk))
                            current_chunk = []
                    
                    # 添加最后一个块
                    if current_chunk:
                        chunks.append('\n'.join(current_chunk))
                    
                    self.log(f"使用段落分块，识别 {len(chunks)} 个段落")
                    
                    # 如果段落太多，合并小段落
                    if len(chunks) > 50:
                        self.log("段落数量过多，尝试合并小段落...")
                        min_length = self.min_content_length_var.get()
                        merged_chunks = []
                        current_merged = []
                        current_length = 0
                        
                        for chunk in chunks:
                            chunk_len = len(chunk)
                            if current_length + chunk_len <= min_length:
                                current_merged.append(chunk)
                                current_length += chunk_len
                            else:
                                if current_merged:
                                    merged_chunks.append('\n'.join(current_merged))
                                current_merged = [chunk]
                                current_length = chunk_len
                        
                        if current_merged:
                            merged_chunks.append('\n'.join(current_merged))
                        
                        self.log(f"合并后段落数量: {len(merged_chunks)}")
                        chunks = merged_chunks
                
                # 如果没有通过段落分块，尝试使用智能分块
                if not chunks and pattern_matches:
                    chunks = self.smart_chunk_text(text)
                    self.log(f"智能分块结果: {len(chunks)} 个块")
                
                # 如果所有方法都失败，将整个文本作为一个块
                if not chunks:
                    self.log("所有分块方法均未产生结果，将整个文本作为一个块处理")
                    chunks = [text.strip()]
            
            # 生成空行连接字符串
            blank_line_str = "\n" * blank_line_count
            self.log(f"设置了 {blank_line_count} 个空行作为分隔")
            
            # 使用空行连接所有块
            processed_text = blank_line_str.join(chunks)
            processed_length = len(processed_text)
            self.log(f"处理后文本长度: {processed_length} 字符")
            
            # 根据选择的输出格式保存文件
            output_format = self.output_format_var.get()
            self.log(f"正在保存为 {output_format} 格式...")
            output_file = os.path.join(output_dir, f"{file_name}_processed.{output_format}")
            
            try:
                if output_format == 'txt':
                    # 以txt格式保存 - 使用UTF-8编码确保正确显示中文
                    with open(output_file, 'w', encoding='utf-8') as f:
                        f.write(processed_text)
                
                elif output_format == 'json':
                    # 以json格式保存
                    data = {
                        "document": file_name,
                        "text": processed_text,
                        "chunks": chunks,
                        "blank_lines_count": blank_line_count
                    }
                    with open(output_file, 'w', encoding='utf-8') as f:
                        json.dump(data, f, ensure_ascii=False, indent=2)
                
                elif output_format == 'md':
                    # 以markdown格式保存
                    with open(output_file, 'w', encoding='utf-8') as f:
                        f.write(processed_text)
                
                elif output_format == 'docx':
                    # 以docx格式保存 - 特别注意空行的添加
                    doc = Document()
                    
                    # 逐个添加块和空行
                    for i, chunk in enumerate(chunks):
                        # 添加当前块的内容
                        if chunk:  # 确保块不是空的
                            doc.add_paragraph(chunk)
                        
                        # 如果不是最后一个块，添加指定数量的空段落
                        if i < len(chunks) - 1:
                            for _ in range(blank_line_count):
                                doc.add_paragraph('')
                    
                    doc.save(output_file)
                
                self.log(f"文件保存成功: {output_file}")
                
                # 验证保存的文件
                file_size = os.path.getsize(output_file) / 1024  # KB
                self.log(f"保存的文件大小: {file_size:.2f} KB")
                
                if file_size == 0:
                    self.log("警告: 保存的文件大小为0")
                
            except Exception as save_error:
                self.log(f"保存文件时出错: {str(save_error)}")
                raise
            
            return len(chunks), output_file
        except Exception as e:
            # 更详细的错误信息记录
            import traceback
            error_type = type(e).__name__
            error_details = str(e)
            trace = traceback.format_exc()
            
            self.log(f"处理文件 {file_path} 出错")
            self.log(f"错误类型: {error_type}")
            self.log(f"错误详情: {error_details}")
            self.log(f"调用堆栈:\n{trace}")
            return 0, None
    
    def process_file_with_timeout(self, file_path, output_dir, chunk_mode, separator, blank_line_count):
        """使用超时机制处理文件"""
        result = [0, None]  # [chunks_count, output_file]
        timeout = self.timeout_var.get()  # 获取超时时间（秒）
        processing_finished = threading.Event()
        processing_error = [None]  # 用于存储处理线程中的错误
        
        def process_thread():
            try:
                # 调用原始处理函数
                chunks_count, output_file = self.process_file(file_path, output_dir, chunk_mode, separator, blank_line_count)
                # 存储结果
                result[0] = chunks_count
                result[1] = output_file
            except Exception as e:
                processing_error[0] = str(e)
                self.log(f"处理线程出错: {str(e)}")
            finally:
                # 无论成功或失败，都标记为处理完成
                processing_finished.set()
        
        # 启动处理线程
        thread = threading.Thread(target=process_thread)
        thread.daemon = True  # 设为守护线程，这样主程序退出时它也会退出
        thread.start()
        
        # 等待处理完成或超时
        is_finished = processing_finished.wait(timeout)
        
        if not is_finished:
            self.log(f"警告: 处理文件 {os.path.basename(file_path)} 超时（{timeout}秒），已跳过")
            self.log("可能的原因: 文件过大、格式复杂或含有特殊内容")
            return 0, None
        
        if processing_error[0]:
            self.log(f"文件处理失败: {processing_error[0]}")
        
        return result[0], result[1]
    
    def _chunking_thread(self, output_dir, chunk_mode, separator, blank_line_count):
        """分块处理线程"""
        try:
            total_files = len(self.file_list)
            total_chunks = 0
            processed_files = []
            skipped_files = []
            
            self.log(f"开始处理 {total_files} 个文件...")
            self.log(f"分块模式: {chunk_mode}")
            self.log(f"空行数量: {blank_line_count}")
            self.log(f"最小内容长度: {self.min_content_length_var.get()} (低于此值的次级内容将合并到上级块)")
            self.log(f"超时时间: {self.timeout_var.get()} 秒")
            self.log(f"输出格式: {self.output_format_var.get()}")
            
            # 将输出目录添加到日志
            self.log(f"输出目录: {output_dir}")
            if not os.path.exists(output_dir):
                os.makedirs(output_dir)
                self.log(f"创建了输出目录: {output_dir}")
            
            for i, file_path in enumerate(self.file_list):
                file_name = os.path.basename(file_path)
                current_index = i + 1
                
                # 更新状态和进度信息
                self.update_status(f"处理中: {file_name} ({current_index}/{total_files})", current_index)
                self.update_file_count(current_index, total_files)
                self.log(f"\n===== 开始处理文件 {current_index}/{total_files}: {file_path} =====")
                
                # 使用带超时的文件处理函数
                chunks_count, output_file = self.process_file_with_timeout(
                    file_path, output_dir, chunk_mode, separator, blank_line_count
                )
                
                if output_file:
                    total_chunks += chunks_count
                    self.update_chunk_count(total_chunks)
                    processed_files.append(output_file)
                    self.log(f"✓ 文件 {file_name} 处理成功，识别 {chunks_count} 个块，已添加空行")
                else:
                    skipped_files.append(file_path)
                    self.log(f"✗ 文件 {file_name} 处理失败或已跳过")
                
                # 更新进度条
                self.progress["value"] = current_index
                
                # 检查是否请求了停止
                if hasattr(self, 'stop_requested') and self.stop_requested:
                    self.log("用户请求停止处理")
                    break
            
            # 汇总处理结果
            self.log("\n===== 处理汇总 =====")
            self.log(f"总文件数: {total_files}")
            self.log(f"成功处理: {len(processed_files)} 个文件")
            self.log(f"总识别块数: {total_chunks} 个块")
            
            if skipped_files:
                self.log(f"失败/跳过: {len(skipped_files)} 个文件:")
                for idx, f in enumerate(skipped_files):
                    self.log(f"  {idx+1}. {os.path.basename(f)}")
                
                # 提供处理失败文件的建议
                self.log("\n===== 故障排除建议 =====")
                self.log("1. 增加超时时间（适用于大型文件）")
                self.log("2. 检查文件是否已损坏或格式特殊")
                self.log("3. 尝试将文件另存为新的Word文档（.docx）后再试")
                self.log("4. 对于复杂格式文档，尝试先复制内容到纯文本编辑器，再保存为新文档")
            
            self.update_status(f"处理完成 {len(processed_files)}/{total_files} 文件, {total_chunks} 块", total_files)
            self.log(f"\n分块处理完成! 成功处理的内容已保存到 {output_dir}")
            
            # 打开输出目录
            if processed_files:
                os.startfile(output_dir)
            
        except Exception as e:
            # 更详细的错误报告
            import traceback
            error_type = type(e).__name__
            error_details = str(e)
            trace = traceback.format_exc()
            
            self.log(f"处理过程中出错")
            self.log(f"错误类型: {error_type}")
            self.log(f"错误详情: {error_details}")
            self.log(f"调用堆栈:\n{trace}")
            
            self.update_status("处理失败")
        finally:
            # 恢复按钮状态
            self.start_btn["state"] = "normal"
            # 添加停止按钮状态恢复（如果有）
            if hasattr(self, 'stop_btn'):
                self.stop_btn["state"] = "disabled"
            self.processing_thread = None
    
    def start_chunking(self):
        """开始分块处理"""
        # 检查输入和输出路径
        if not self.file_list:
            messagebox.showwarning("警告", "请先选择文件或文件夹")
            return
        
        output_dir = self.output_var.get()
        if not output_dir:
            messagebox.showwarning("警告", "请选择输出目录")
            return
        
        # 获取分块参数
        chunk_mode = self.chunk_mode_combo.get()
        separator = self.separator_var.get()
        blank_line_count = self.blank_line_count_var.get()
        timeout = self.timeout_var.get()
        
        if chunk_mode == '按分隔符' and not separator:
            messagebox.showwarning("警告", "使用分隔符模式时，请输入分隔符")
            return
        
        # 禁用开始按钮，启用停止按钮
        self.start_btn["state"] = "disabled"
        self.stop_btn["state"] = "normal"
        
        # 重置停止标志
        self.stop_requested = False
        
        # 清空进度条
        self.progress["value"] = 0
        self.progress["maximum"] = len(self.file_list)
        
        # 在新线程中处理分块，避免UI冻结
        self.processing_thread = threading.Thread(
            target=self._chunking_thread, 
            args=(output_dir, chunk_mode, separator, blank_line_count)
        )
        self.processing_thread.daemon = True
        self.processing_thread.start()
    
    def stop_chunking(self):
        """停止分块处理"""
        if self.processing_thread and self.processing_thread.is_alive():
            self.stop_requested = True
            self.log("请求停止处理，等待当前文件处理完成...")
            self.stop_btn["state"] = "disabled"


if __name__ == "__main__":
    root = tk.Tk()
    app = ProductTextChunker(root)
    root.mainloop() 